msg_tool\scripts\qlie\image/
abmp10.rs

1//! Qlie Abmp10/11/12 image (.b)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::files::*;
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::io::{Read, Seek, Write};
10
11#[derive(Debug)]
12/// Qlie Abmp10/11/12 image builder
13pub struct Abmp10ImageBuilder {}
14
15impl Abmp10ImageBuilder {
16    pub fn new() -> Self {
17        Self {}
18    }
19}
20
21impl ScriptBuilder for Abmp10ImageBuilder {
22    fn default_encoding(&self) -> Encoding {
23        Encoding::Cp932
24    }
25
26    fn build_script(
27        &self,
28        buf: Vec<u8>,
29        _filename: &str,
30        encoding: Encoding,
31        _archive_encoding: Encoding,
32        config: &ExtraConfig,
33        _archive: Option<&Box<dyn Script>>,
34    ) -> Result<Box<dyn Script>> {
35        Ok(Box::new(Abmp10Image::new(
36            MemReader::new(buf),
37            encoding,
38            config,
39        )?))
40    }
41
42    fn extensions(&self) -> &'static [&'static str] {
43        &["b"]
44    }
45
46    fn script_type(&self) -> &'static ScriptType {
47        &ScriptType::QlieAbmp10
48    }
49
50    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
51        if buf_len >= 6 && buf.starts_with(b"abmp1") {
52            let v = buf[5];
53            if v >= b'0' && v <= b'2' {
54                return Some(25);
55            }
56        }
57        None
58    }
59
60    fn can_create_file(&self) -> bool {
61        true
62    }
63
64    fn create_file<'a>(
65        &'a self,
66        filename: &'a str,
67        writer: Box<dyn WriteSeek + 'a>,
68        encoding: Encoding,
69        file_encoding: Encoding,
70        config: &ExtraConfig,
71    ) -> Result<()> {
72        create_file(filename, writer, encoding, file_encoding, config)
73    }
74}
75
76trait AbmpRes {
77    fn read_from<T: Read + Seek>(
78        data: &mut T,
79        encoding: Encoding,
80        img: &mut AbmpImage,
81    ) -> Result<Self>
82    where
83        Self: Sized;
84    fn write_to<T: Write + Seek>(
85        &self,
86        data: &mut T,
87        encoding: Encoding,
88        img: &AbmpImage,
89    ) -> Result<()>;
90}
91
92#[derive(Clone, Debug, Serialize, Deserialize)]
93struct ResourceRef {
94    index: usize,
95}
96
97#[derive(Clone, Debug, Serialize, Deserialize)]
98struct AbData {
99    /// abdataxx xx = version
100    tag: String,
101    data: ResourceRef,
102}
103
104impl AbmpRes for AbData {
105    fn read_from<T: Read + Seek>(
106        data: &mut T,
107        encoding: Encoding,
108        img: &mut AbmpImage,
109    ) -> Result<Self>
110    where
111        Self: Sized,
112    {
113        let tag = data.read_fstring(0x10, encoding, true)?;
114        if !tag.starts_with("abdata") {
115            anyhow::bail!("Invalid AbData tag: {}", tag);
116        }
117        let size = data.read_u32()?;
118        let resource = data.read_exact_vec(size as usize)?;
119        img.resources.push(resource);
120        let index = img.resources.len() - 1;
121        img.resource_filenames.push(format!("{tag}_{index}"));
122        Ok(AbData {
123            tag,
124            data: ResourceRef { index },
125        })
126    }
127
128    fn write_to<T: Write + Seek>(
129        &self,
130        data: &mut T,
131        encoding: Encoding,
132        img: &AbmpImage,
133    ) -> Result<()> {
134        data.write_fstring(&self.tag, 0x10, encoding, 0, false)?;
135        let res = img
136            .resources
137            .get(self.data.index)
138            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
139        data.write_u32(res.len() as u32)?;
140        data.write_all(res)?;
141        Ok(())
142    }
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize)]
146/// tag: abimage10
147struct AbImage10 {
148    datas: Vec<AbmpResource>,
149}
150
151impl AbmpRes for AbImage10 {
152    fn read_from<T: Read + Seek>(
153        data: &mut T,
154        encoding: Encoding,
155        img: &mut AbmpImage,
156    ) -> Result<Self>
157    where
158        Self: Sized,
159    {
160        let tag = data.read_fstring(0x10, encoding, true)?;
161        if tag != "abimage10" {
162            anyhow::bail!("Invalid AbImage10 tag: {}", tag);
163        }
164        let mut datas = Vec::new();
165        let count = data.read_u8()?;
166        for _ in 0..count {
167            let data = AbmpResource::read_from(data, encoding, img)?;
168            datas.push(data);
169        }
170        Ok(AbImage10 { datas })
171    }
172
173    fn write_to<T: Write + Seek>(
174        &self,
175        data: &mut T,
176        encoding: Encoding,
177        img: &AbmpImage,
178    ) -> Result<()> {
179        data.write_fstring("abimage10", 0x10, encoding, 0, false)?;
180        data.write_u8(self.datas.len() as u8)?;
181        for res in &self.datas {
182            res.write_to(data, encoding, img)?;
183        }
184        Ok(())
185    }
186}
187
188#[derive(Clone, Debug, Serialize, Deserialize)]
189/// tag: absound10
190struct AbSound10 {
191    datas: Vec<AbmpResource>,
192}
193
194impl AbmpRes for AbSound10 {
195    fn read_from<T: Read + Seek>(
196        data: &mut T,
197        encoding: Encoding,
198        img: &mut AbmpImage,
199    ) -> Result<Self>
200    where
201        Self: Sized,
202    {
203        let tag = data.read_fstring(0x10, encoding, true)?;
204        if tag != "absound10" {
205            anyhow::bail!("Invalid AbSound10 tag: {}", tag);
206        }
207        let mut datas = Vec::new();
208        let count = data.read_u8()?;
209        for _ in 0..count {
210            let data = AbmpResource::read_from(data, encoding, img)?;
211            datas.push(data);
212        }
213        Ok(AbSound10 { datas })
214    }
215
216    fn write_to<T: Write + Seek>(
217        &self,
218        data: &mut T,
219        encoding: Encoding,
220        img: &AbmpImage,
221    ) -> Result<()> {
222        data.write_fstring("absound10", 0x10, encoding, 0, false)?;
223        data.write_u8(self.datas.len() as u8)?;
224        for res in &self.datas {
225            res.write_to(data, encoding, img)?;
226        }
227        Ok(())
228    }
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize)]
232/// tag: abimgdat15
233struct AbImgData15 {
234    version: u32,
235    name: String,
236    internal_name: String,
237    typ: u8,
238    param: Vec<u8>,
239    data: ResourceRef,
240}
241
242impl AbmpRes for AbImgData15 {
243    fn read_from<T: Read + Seek>(
244        data: &mut T,
245        encoding: Encoding,
246        img: &mut AbmpImage,
247    ) -> Result<Self>
248    where
249        Self: Sized,
250    {
251        let tag = data.read_fstring(0x10, encoding, true)?;
252        if tag != "abimgdat15" {
253            anyhow::bail!("Invalid AbImgData15 tag: {}", tag);
254        }
255        let version = data.read_u32()?;
256        let name_length = data.read_u16()? as usize * 2;
257        let name = data.read_fstring(name_length, Encoding::Utf16LE, false)?;
258        let internal_name_length = data.read_u16()? as usize;
259        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
260        let typ = data.read_u8()?;
261        let param_size = if version == 2 { 0x1d } else { 0x11 };
262        let param = data.read_exact_vec(param_size)?;
263        let size = data.read_u32()?;
264        let resource = data.read_exact_vec(size as usize)?;
265        img.resources.push(resource);
266        let index = img.resources.len() - 1;
267        let mut nname = if !name.is_empty() {
268            name.clone()
269        } else if !internal_name.is_empty() {
270            internal_name.clone()
271        } else {
272            format!("abimage15_{index}")
273        };
274        match typ {
275            0 => nname.push_str(".bmp"),
276            1 => nname.push_str(".jpg"),
277            2 | 3 => nname.push_str(".png"),
278            4 => nname.push_str(".m"),
279            5 => nname.push_str(".argb"),
280            6 => nname.push_str(".b"),
281            7 => nname.push_str(".ogv"),
282            8 => nname.push_str(".mdl"),
283            _ => {}
284        }
285        img.resource_filenames.push(nname);
286        Ok(AbImgData15 {
287            version,
288            name,
289            internal_name,
290            typ,
291            param,
292            data: ResourceRef { index },
293        })
294    }
295
296    fn write_to<T: Write + Seek>(
297        &self,
298        data: &mut T,
299        encoding: Encoding,
300        img: &AbmpImage,
301    ) -> Result<()> {
302        data.write_fstring("abimgdat15", 0x10, encoding, 0, false)?;
303        data.write_u32(self.version)?;
304        let name_length = self.name.encode_utf16().count() as u16;
305        let name = encode_string(Encoding::Utf16LE, &self.name, true)?;
306        if name.len() != (name_length as usize) * 2 {
307            anyhow::bail!("Name length mismatch when writing AbImgData15");
308        }
309        data.write_u16(name_length)?;
310        data.write_all(&name)?;
311        let internal_name = encode_string(encoding, &self.internal_name, true)?;
312        data.write_u16(internal_name.len() as u16)?;
313        data.write_all(&internal_name)?;
314        data.write_u8(self.typ)?;
315        let param_size = if self.version == 2 { 0x1d } else { 0x11 };
316        if self.param.len() != param_size {
317            anyhow::bail!("Param size mismatch when writing AbImgData15");
318        }
319        data.write_all(&self.param)?;
320        let res = img
321            .resources
322            .get(self.data.index)
323            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
324        data.write_u32(res.len() as u32)?;
325        data.write_all(res)?;
326        Ok(())
327    }
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize)]
331/// tag: abimgdat14
332struct AbImgData14 {
333    name: String,
334    internal_name: String,
335    typ: u8,
336    param: Vec<u8>,
337    data: ResourceRef,
338}
339
340impl AbmpRes for AbImgData14 {
341    fn read_from<T: Read + Seek>(
342        data: &mut T,
343        encoding: Encoding,
344        img: &mut AbmpImage,
345    ) -> Result<Self>
346    where
347        Self: Sized,
348    {
349        let tag = data.read_fstring(0x10, encoding, true)?;
350        if tag != "abimgdat14" {
351            anyhow::bail!("Invalid AbImgData14 tag: {}", tag);
352        }
353        let name_length = data.read_u16()? as usize;
354        let name = data.read_fstring(name_length, encoding, false)?;
355        let internal_name_length = data.read_u16()? as usize;
356        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
357        let typ = data.read_u8()?;
358        let param = data.read_exact_vec(0x4C)?;
359        let size = data.read_u32()?;
360        let resource = data.read_exact_vec(size as usize)?;
361        img.resources.push(resource);
362        let index = img.resources.len() - 1;
363        let mut nname = if !name.is_empty() {
364            name.clone()
365        } else if !internal_name.is_empty() {
366            internal_name.clone()
367        } else {
368            format!("abimage14_{index}")
369        };
370        match typ {
371            0 => nname.push_str(".bmp"),
372            1 => nname.push_str(".jpg"),
373            2 | 3 => nname.push_str(".png"),
374            4 => nname.push_str(".m"),
375            5 => nname.push_str(".argb"),
376            6 => nname.push_str(".b"),
377            7 => nname.push_str(".ogv"),
378            8 => nname.push_str(".mdl"),
379            _ => {}
380        }
381        img.resource_filenames.push(nname);
382        Ok(AbImgData14 {
383            name,
384            internal_name,
385            typ,
386            param,
387            data: ResourceRef { index },
388        })
389    }
390
391    fn write_to<T: Write + Seek>(
392        &self,
393        data: &mut T,
394        encoding: Encoding,
395        img: &AbmpImage,
396    ) -> Result<()> {
397        data.write_fstring("abimgdat14", 0x10, encoding, 0, false)?;
398        let name = encode_string(encoding, &self.name, true)?;
399        data.write_u16(name.len() as u16)?;
400        data.write_all(&name)?;
401        let internal_name = encode_string(encoding, &self.internal_name, true)?;
402        data.write_u16(internal_name.len() as u16)?;
403        data.write_all(&internal_name)?;
404        data.write_u8(self.typ)?;
405        if self.param.len() != 0x4C {
406            anyhow::bail!("Param size mismatch when writing AbImgData14");
407        }
408        data.write_all(&self.param)?;
409        let res = img
410            .resources
411            .get(self.data.index)
412            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
413        data.write_u32(res.len() as u32)?;
414        data.write_all(res)?;
415        Ok(())
416    }
417}
418
419#[derive(Clone, Debug, Serialize, Deserialize)]
420/// tag: abimgdat13
421struct AbImgData13 {
422    name: String,
423    internal_name: String,
424    typ: u8,
425    param: Vec<u8>,
426    data: ResourceRef,
427}
428
429impl AbmpRes for AbImgData13 {
430    fn read_from<T: Read + Seek>(
431        data: &mut T,
432        encoding: Encoding,
433        img: &mut AbmpImage,
434    ) -> Result<Self>
435    where
436        Self: Sized,
437    {
438        let tag = data.read_fstring(0x10, encoding, true)?;
439        if tag != "abimgdat13" {
440            anyhow::bail!("Invalid AbImgData13 tag: {}", tag);
441        }
442        let name_length = data.read_u16()? as usize;
443        let name = data.read_fstring(name_length, encoding, false)?;
444        let internal_name_length = data.read_u16()? as usize;
445        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
446        let typ = data.read_u8()?;
447        let param = data.read_exact_vec(0xC)?;
448        let size = data.read_u32()?;
449        let resource = data.read_exact_vec(size as usize)?;
450        img.resources.push(resource);
451        let index = img.resources.len() - 1;
452        let mut nname = if !name.is_empty() {
453            name.clone()
454        } else if !internal_name.is_empty() {
455            internal_name.clone()
456        } else {
457            format!("abimage13_{index}")
458        };
459        match typ {
460            0 => nname.push_str(".bmp"),
461            1 => nname.push_str(".jpg"),
462            2 | 3 => nname.push_str(".png"),
463            4 => nname.push_str(".m"),
464            5 => nname.push_str(".argb"),
465            6 => nname.push_str(".b"),
466            7 => nname.push_str(".ogv"),
467            8 => nname.push_str(".mdl"),
468            _ => {}
469        }
470        img.resource_filenames.push(nname);
471        Ok(AbImgData13 {
472            name,
473            internal_name,
474            typ,
475            param,
476            data: ResourceRef { index },
477        })
478    }
479
480    fn write_to<T: Write + Seek>(
481        &self,
482        data: &mut T,
483        encoding: Encoding,
484        img: &AbmpImage,
485    ) -> Result<()> {
486        data.write_fstring("abimgdat13", 0x10, encoding, 0, false)?;
487        let name = encode_string(encoding, &self.name, true)?;
488        data.write_u16(name.len() as u16)?;
489        data.write_all(&name)?;
490        let internal_name = encode_string(encoding, &self.internal_name, true)?;
491        data.write_u16(internal_name.len() as u16)?;
492        data.write_all(&internal_name)?;
493        data.write_u8(self.typ)?;
494        if self.param.len() != 0xC {
495            anyhow::bail!("Param size mismatch when writing AbImgData13");
496        }
497        data.write_all(&self.param)?;
498        let res = img
499            .resources
500            .get(self.data.index)
501            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
502        data.write_u32(res.len() as u32)?;
503        data.write_all(res)?;
504        Ok(())
505    }
506}
507
508#[derive(Clone, Debug, Serialize, Deserialize)]
509/// tag: abimgdat11
510struct AbImgData11 {
511    name: String,
512    internal_name: String,
513    typ: u8,
514    data: ResourceRef,
515}
516
517impl AbmpRes for AbImgData11 {
518    fn read_from<T: Read + Seek>(
519        data: &mut T,
520        encoding: Encoding,
521        img: &mut AbmpImage,
522    ) -> Result<Self>
523    where
524        Self: Sized,
525    {
526        let tag = data.read_fstring(0x10, encoding, true)?;
527        if tag != "abimgdat11" {
528            anyhow::bail!("Invalid AbImgData11 tag: {}", tag);
529        }
530        let name_length = data.read_u16()? as usize;
531        let name = data.read_fstring(name_length, encoding, false)?;
532        let internal_name_length = data.read_u16()? as usize;
533        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
534        let typ = data.read_u8()?;
535        let size = data.read_u32()?;
536        let resource = data.read_exact_vec(size as usize)?;
537        img.resources.push(resource);
538        let index = img.resources.len() - 1;
539        let mut nname = if !name.is_empty() {
540            name.clone()
541        } else if !internal_name.is_empty() {
542            internal_name.clone()
543        } else {
544            format!("abimage11_{index}")
545        };
546        match typ {
547            0 => nname.push_str(".bmp"),
548            1 => nname.push_str(".jpg"),
549            2 | 3 => nname.push_str(".png"),
550            4 => nname.push_str(".m"),
551            5 => nname.push_str(".argb"),
552            6 => nname.push_str(".b"),
553            7 => nname.push_str(".ogv"),
554            8 => nname.push_str(".mdl"),
555            _ => {}
556        }
557        img.resource_filenames.push(nname);
558        Ok(AbImgData11 {
559            name,
560            internal_name,
561            typ,
562            data: ResourceRef { index },
563        })
564    }
565
566    fn write_to<T: Write + Seek>(
567        &self,
568        data: &mut T,
569        encoding: Encoding,
570        img: &AbmpImage,
571    ) -> Result<()> {
572        data.write_fstring("abimgdat11", 0x10, encoding, 0, false)?;
573        let name = encode_string(encoding, &self.name, true)?;
574        data.write_u16(name.len() as u16)?;
575        data.write_all(&name)?;
576        let internal_name = encode_string(encoding, &self.internal_name, true)?;
577        data.write_u16(internal_name.len() as u16)?;
578        data.write_all(&internal_name)?;
579        data.write_u8(self.typ)?;
580        let res = img
581            .resources
582            .get(self.data.index)
583            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
584        data.write_u32(res.len() as u32)?;
585        data.write_all(res)?;
586        Ok(())
587    }
588}
589
590#[derive(Clone, Debug, Serialize, Deserialize)]
591/// tag: absnddat12
592struct AbSndData12 {
593    version: u32,
594    name: String,
595    internal_name: String,
596    typ: u8,
597    data: ResourceRef,
598}
599
600impl AbmpRes for AbSndData12 {
601    fn read_from<T: Read + Seek>(
602        data: &mut T,
603        encoding: Encoding,
604        img: &mut AbmpImage,
605    ) -> Result<Self>
606    where
607        Self: Sized,
608    {
609        let tag = data.read_fstring(0x10, encoding, true)?;
610        if tag != "absnddat12" {
611            anyhow::bail!("Invalid AbSndData12 tag: {}", tag);
612        }
613        let version = data.read_u32()?;
614        let name_length = data.read_u16()? as usize * 2;
615        let name = data.read_fstring(name_length, Encoding::Utf16LE, false)?;
616        let internal_name_length = data.read_u16()? as usize;
617        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
618        let typ = data.read_u8()?;
619        let size = data.read_u32()?;
620        let resource = data.read_exact_vec(size as usize)?;
621        img.resources.push(resource);
622        let index = img.resources.len() - 1;
623        let mut nname = if !name.is_empty() {
624            name.clone()
625        } else if !internal_name.is_empty() {
626            internal_name.clone()
627        } else {
628            format!("absnddat12_{index}")
629        };
630        match typ {
631            1 => nname.push_str(".ogg"),
632            _ => {}
633        }
634        img.resource_filenames.push(nname);
635        Ok(AbSndData12 {
636            version,
637            name,
638            internal_name,
639            typ,
640            data: ResourceRef { index },
641        })
642    }
643
644    fn write_to<T: Write + Seek>(
645        &self,
646        data: &mut T,
647        encoding: Encoding,
648        img: &AbmpImage,
649    ) -> Result<()> {
650        data.write_fstring("absnddat12", 0x10, encoding, 0, false)?;
651        data.write_u32(self.version)?;
652        let name_length = self.name.encode_utf16().count() as u16;
653        let name = encode_string(Encoding::Utf16LE, &self.name, true)?;
654        if name.len() != (name_length as usize) * 2 {
655            anyhow::bail!("Name length mismatch when writing AbSndData12");
656        }
657        data.write_u16(name_length)?;
658        data.write_all(&name)?;
659        let internal_name = encode_string(encoding, &self.internal_name, true)?;
660        data.write_u16(internal_name.len() as u16)?;
661        data.write_all(&internal_name)?;
662        data.write_u8(self.typ)?;
663        let res = img
664            .resources
665            .get(self.data.index)
666            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
667        data.write_u32(res.len() as u32)?;
668        data.write_all(res)?;
669        Ok(())
670    }
671}
672
673#[derive(Clone, Debug, Serialize, Deserialize)]
674/// tag: absnddat11
675struct AbSndData11 {
676    name: String,
677    internal_name: String,
678    typ: u8,
679    data: ResourceRef,
680}
681
682impl AbmpRes for AbSndData11 {
683    fn read_from<T: Read + Seek>(
684        data: &mut T,
685        encoding: Encoding,
686        img: &mut AbmpImage,
687    ) -> Result<Self>
688    where
689        Self: Sized,
690    {
691        let tag = data.read_fstring(0x10, encoding, true)?;
692        if tag != "absnddat11" {
693            anyhow::bail!("Invalid AbSndData11 tag: {}", tag);
694        }
695        let name_length = data.read_u16()? as usize;
696        let name = data.read_fstring(name_length, encoding, false)?;
697        let internal_name_length = data.read_u16()? as usize;
698        let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
699        let typ = data.read_u8()?;
700        let size = data.read_u32()?;
701        let resource = data.read_exact_vec(size as usize)?;
702        img.resources.push(resource);
703        let index = img.resources.len() - 1;
704        let mut nname = if !name.is_empty() {
705            name.clone()
706        } else if !internal_name.is_empty() {
707            internal_name.clone()
708        } else {
709            format!("absnddat11_{index}")
710        };
711        match typ {
712            1 => nname.push_str(".ogg"),
713            _ => {}
714        }
715        img.resource_filenames.push(nname);
716        Ok(AbSndData11 {
717            name,
718            internal_name,
719            typ,
720            data: ResourceRef { index },
721        })
722    }
723
724    fn write_to<T: Write + Seek>(
725        &self,
726        data: &mut T,
727        encoding: Encoding,
728        img: &AbmpImage,
729    ) -> Result<()> {
730        data.write_fstring("absnddat11", 0x10, encoding, 0, false)?;
731        let name = encode_string(encoding, &self.name, true)?;
732        data.write_u16(name.len() as u16)?;
733        data.write_all(&name)?;
734        let internal_name = encode_string(encoding, &self.internal_name, true)?;
735        data.write_u16(internal_name.len() as u16)?;
736        data.write_all(&internal_name)?;
737        data.write_u8(self.typ)?;
738        let res = img
739            .resources
740            .get(self.data.index)
741            .ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
742        data.write_u32(res.len() as u32)?;
743        data.write_all(res)?;
744        Ok(())
745    }
746}
747
748#[derive(Clone, Debug, Serialize, Deserialize)]
749#[serde(tag = "@type")]
750enum AbmpResource {
751    Data(AbData),
752    Image10(AbImage10),
753    ImgData15(AbImgData15),
754    ImgData14(AbImgData14),
755    ImgData13(AbImgData13),
756    ImgData11(AbImgData11),
757    Sound10(AbSound10),
758    SndData12(AbSndData12),
759    SndData11(AbSndData11),
760}
761
762impl AbmpRes for AbmpResource {
763    fn read_from<T: Read + Seek>(
764        data: &mut T,
765        encoding: Encoding,
766        img: &mut AbmpImage,
767    ) -> Result<Self> {
768        let tag = data.peek_fstring(0x10, encoding, true)?;
769        if tag.starts_with("abdata") {
770            return Ok(AbmpResource::Data(AbData::read_from(data, encoding, img)?));
771        }
772        match tag.as_str() {
773            "abimage10" => Ok(AbmpResource::Image10(AbImage10::read_from(
774                data, encoding, img,
775            )?)),
776            "abimgdat15" => Ok(AbmpResource::ImgData15(AbImgData15::read_from(
777                data, encoding, img,
778            )?)),
779            "abimgdat14" => Ok(AbmpResource::ImgData14(AbImgData14::read_from(
780                data, encoding, img,
781            )?)),
782            "abimgdat13" => Ok(AbmpResource::ImgData13(AbImgData13::read_from(
783                data, encoding, img,
784            )?)),
785            "abimgdat11" => Ok(AbmpResource::ImgData11(AbImgData11::read_from(
786                data, encoding, img,
787            )?)),
788            "absound10" => Ok(AbmpResource::Sound10(AbSound10::read_from(
789                data, encoding, img,
790            )?)),
791            "absnddat11" => Ok(AbmpResource::SndData11(AbSndData11::read_from(
792                data, encoding, img,
793            )?)),
794            "absnddat12" => Ok(AbmpResource::SndData12(AbSndData12::read_from(
795                data, encoding, img,
796            )?)),
797            _ => {
798                anyhow::bail!("Unknown Abmp resource tag: {}", tag);
799            }
800        }
801    }
802
803    fn write_to<T: Write + Seek>(
804        &self,
805        data: &mut T,
806        encoding: Encoding,
807        img: &AbmpImage,
808    ) -> Result<()> {
809        match self {
810            AbmpResource::Data(res) => res.write_to(data, encoding, img),
811            AbmpResource::Image10(res) => res.write_to(data, encoding, img),
812            AbmpResource::ImgData15(res) => res.write_to(data, encoding, img),
813            AbmpResource::ImgData14(res) => res.write_to(data, encoding, img),
814            AbmpResource::ImgData13(res) => res.write_to(data, encoding, img),
815            AbmpResource::ImgData11(res) => res.write_to(data, encoding, img),
816            AbmpResource::Sound10(res) => res.write_to(data, encoding, img),
817            AbmpResource::SndData12(res) => res.write_to(data, encoding, img),
818            AbmpResource::SndData11(res) => res.write_to(data, encoding, img),
819        }
820    }
821}
822
823/// Qlie Abmp10/11/12 image
824#[derive(Clone, Debug, Serialize, Deserialize)]
825struct AbmpImage {
826    /// Valid version: 10, 11, 12
827    version: u8,
828    datas: Vec<AbmpResource>,
829    extra: Vec<u8>,
830    #[serde(skip)]
831    resources: Vec<Vec<u8>>,
832    /// Just used for dump
833    #[serde(skip)]
834    resource_filenames: Vec<String>,
835}
836
837fn is_false(b: &bool) -> bool {
838    !*b
839}
840
841#[derive(Clone, Debug, Serialize, Deserialize)]
842struct Resource {
843    path: String,
844    #[serde(skip_serializing_if = "is_false", default)]
845    ambp10: bool,
846}
847
848#[derive(Clone, Debug, Serialize, Deserialize)]
849struct AbmpImage2 {
850    version: u8,
851    datas: Vec<AbmpResource>,
852    #[serde(skip_serializing_if = "Vec::is_empty", default)]
853    extra: Vec<u8>,
854    resources: Vec<Resource>,
855}
856
857impl AbmpImage {
858    pub fn new_from<T: Read + Seek>(reader: &mut T, encoding: Encoding) -> Result<Self> {
859        let magic = reader.read_fstring(16, encoding, true)?;
860        if !magic.starts_with("abmp1") {
861            anyhow::bail!("Not a valid Abmp image");
862        }
863        let version = magic.as_bytes()[5] - b'0' + 10;
864        let mut img = AbmpImage {
865            version,
866            datas: Vec::new(),
867            resources: Vec::new(),
868            resource_filenames: Vec::new(),
869            extra: Vec::new(),
870        };
871        let len = reader.stream_length()?;
872        let mut pos = reader.stream_position()?;
873        while pos < len - 16 {
874            let data = AbmpResource::read_from(reader, encoding, &mut img)?;
875            img.datas.push(data);
876            pos = reader.stream_position()?;
877        }
878        if pos < len {
879            img.extra = reader.read_exact_vec((len - pos) as usize)?;
880        }
881        Ok(img)
882    }
883
884    pub fn dump_to<T: Write + Seek>(&self, mut writer: T, encoding: Encoding) -> Result<()> {
885        writer.write_fstring(
886            &format!("abmp1{}", (self.version - 10 + b'0') as char),
887            16,
888            encoding,
889            0,
890            false,
891        )?;
892        for data in &self.datas {
893            data.write_to(&mut writer, encoding, self)?;
894        }
895        writer.write_all(&self.extra)?;
896        Ok(())
897    }
898
899    fn to_image2(&self) -> AbmpImage2 {
900        AbmpImage2 {
901            version: self.version,
902            datas: self.datas.clone(),
903            resources: Vec::new(),
904            extra: self.extra.clone(),
905        }
906    }
907
908    fn from_image2(img: &AbmpImage2) -> Self {
909        AbmpImage {
910            version: img.version,
911            datas: img.datas.clone(),
912            resources: Vec::new(),
913            resource_filenames: Vec::new(),
914            extra: img.extra.clone(),
915        }
916    }
917}
918
919#[derive(Debug)]
920pub struct Abmp10Image {
921    img: AbmpImage,
922    encoding: Encoding,
923    config: ExtraConfig,
924}
925
926impl Abmp10Image {
927    pub fn new<T: Read + Seek>(
928        mut data: T,
929        encoding: Encoding,
930        config: &ExtraConfig,
931    ) -> Result<Self> {
932        let img = AbmpImage::new_from(&mut data, encoding)?;
933        Ok(Abmp10Image {
934            img,
935            encoding,
936            config: config.clone(),
937        })
938    }
939
940    fn output_resource(
941        &self,
942        folder_path: &std::path::PathBuf,
943        path: String,
944        data: &[u8],
945        encoding: Encoding,
946    ) -> Result<Resource> {
947        let mut res = Resource {
948            path,
949            ambp10: false,
950        };
951        if self.config.qlie_abmp10_process_abmp10
952            && data.len() > 6
953            && data.starts_with(b"abmp1")
954            && data[5] >= b'0'
955            && data[5] <= b'2'
956        {
957            res.ambp10 = true;
958            let another = Abmp10Image::new(MemReaderRef::new(data), self.encoding, &self.config)?;
959            let mut np = std::path::PathBuf::from(&res.path);
960            np.set_extension(another.custom_output_extension());
961            res.path = np.to_string_lossy().to_string();
962            let path = folder_path.join(&res.path);
963            make_sure_dir_exists(&path)?;
964            another.custom_export(&path, encoding)?;
965        } else {
966            let path = folder_path.join(&res.path);
967            make_sure_dir_exists(&path)?;
968            std::fs::write(&path, data)?;
969        }
970        Ok(res)
971    }
972}
973
974impl Script for Abmp10Image {
975    fn default_output_script_type(&self) -> OutputScriptType {
976        OutputScriptType::Custom
977    }
978
979    fn is_output_supported(&self, output: OutputScriptType) -> bool {
980        matches!(output, OutputScriptType::Custom)
981    }
982
983    fn default_format_type(&self) -> FormatOptions {
984        FormatOptions::None
985    }
986
987    fn custom_output_extension<'a>(&'a self) -> &'a str {
988        if self.config.custom_yaml {
989            "yaml"
990        } else {
991            "json"
992        }
993    }
994
995    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
996        let file = std::fs::File::create(filename)?;
997        let mut file = std::io::BufWriter::new(file);
998        let mut img = self.img.to_image2();
999        let mut base_path = filename.to_path_buf();
1000        base_path.set_extension("");
1001        for (res, res_name) in self
1002            .img
1003            .resources
1004            .iter()
1005            .zip(self.img.resource_filenames.iter())
1006        {
1007            let res_name = sanitize_path(res_name);
1008            let res = self.output_resource(&base_path, res_name, res, encoding)?;
1009            img.resources.push(res);
1010        }
1011        let s = if self.config.custom_yaml {
1012            serde_yaml_ng::to_string(&img)?
1013        } else {
1014            serde_json::to_string_pretty(&img)?
1015        };
1016        let s = encode_string(encoding, &s, false)?;
1017        file.write_all(&s)?;
1018        Ok(())
1019    }
1020
1021    fn custom_import<'a>(
1022        &'a self,
1023        custom_filename: &'a str,
1024        file: Box<dyn WriteSeek + 'a>,
1025        encoding: Encoding,
1026        output_encoding: Encoding,
1027    ) -> Result<()> {
1028        create_file(
1029            custom_filename,
1030            file,
1031            encoding,
1032            output_encoding,
1033            &self.config,
1034        )
1035    }
1036}
1037
1038fn create_file<'a>(
1039    filename: &str,
1040    mut writer: Box<dyn WriteSeek + 'a>,
1041    encoding: Encoding,
1042    file_encoding: Encoding,
1043    config: &ExtraConfig,
1044) -> Result<()> {
1045    let data = crate::utils::files::read_file(filename)?;
1046    let s = decode_to_string(file_encoding, &data, true)?;
1047    let img2: AbmpImage2 = if config.custom_yaml {
1048        serde_yaml_ng::from_str(&s)?
1049    } else {
1050        serde_json::from_str(&s)?
1051    };
1052    let mut img = AbmpImage::from_image2(&img2);
1053    let mut base_path = std::path::PathBuf::from(filename);
1054    base_path.set_extension("");
1055    for res in &img2.resources {
1056        let path = base_path.join(&res.path);
1057        let buf = if res.ambp10 {
1058            let mut mem = MemWriter::new();
1059            create_file(
1060                &path.to_string_lossy(),
1061                Box::new(&mut mem),
1062                encoding,
1063                file_encoding,
1064                config,
1065            )?;
1066            mem.into_inner()
1067        } else {
1068            crate::utils::files::read_file(&path)?
1069        };
1070        img.resources.push(buf);
1071    }
1072    img.dump_to(&mut writer, encoding)?;
1073    Ok(())
1074}